﻿#Requires AutoHotkey v2.0
#SingleInstance Force

TraySetIcon '.\MeowPaste.ico'
CoordMode 'Mouse', 'Screen'

#Include g:\AHK\git-ahk-lib\Extend.ahk
#Include g:\AHK\git-ahk-lib\Tip.ahk
#Include g:\AHK\git-ahk-lib\Theme.ahk
#Include g:\AHK\git-ahk-lib\util\log\log.ahk
#Include g:\AHK\git-ahk-lib\util\Cursor.ahk
#Include g:\AHK\git-ahk-lib\util\config\CustomFSEx.ahk
#Include g:\AHK\git-ahk-lib\lib\gdip\GdipStarter.ahk
#Include .\core\Init.ahk
#Include .\core\PasteGui.ahk
#Include .\core\FullScreenGui.ahk
#Include .\core\History.ahk
#Include .\core\HistoryGui.ahk
#Include .\core\Zoom.ahk
#Include .\core\Hook.ahk
#Include .\core\Saveto.ahk

logger := Log(logfilePath := './log.txt')
logger.level := Log.LogLevel.Info
OnError((e, *) => (logger.Err(e.What ':' e.Message ':' e.File ':' e.Line), 0))

cfs := CustomFSEx.Of('./config.txt'), cfg := Init(cfs), History.Init(cfg.hisPath)

#Include .\plugin\Plugin.ahk
#Include .\plugin\TimestampGenerator.ahk

tsp := TimestampPlugin(, 150, 'Lime')
Plugin.Load(tsp), Plugin.Exec()

#Include .\ui\MyToolBar.ahk
#Include .\ui\Helper.ahk
#Include .\ui\Setting.ahk
#Include .\util\BMP_OP.ahk

isReady := true

tray := A_TrayMenu
  , tray.delete()
  , tray.add('截图', (*) => Start((*) => Sleep(300)))
  , tray.add('3s后截图', (*) => Start((*) => Delay(3)))
  , tray.add('打开上一张截图', (*) => PasteHistory())
  , tray.add('打开位图文件', (*) => PasteBMPFile(FileSelect(, cfg.defaultSavePath, , '(*.bmp)')))
  , tray.add()
  , tray.add('设置', (*) => Setting.Show())
  , tray.add('打开文件夹', (*) => Run(A_ScriptDir))
  , tray.add('打开日志', (*) => Run(A_ScriptDir '/' logfilePath))
  , tray.add()
  , tray.add('帮助', (*) => Helper.Show())
  , tray.add()
  , tray.add('重新启动', (*) => Reload())
  , tray.add("退出", (*) => ExitApp())
  , tray.ClickCount := cfg.clickCount
  , tray.Default := '截图'

Hotkey cfg.clipHK, (*) => Start()
Hotkey cfg.clearAllHK, ClearAll
HotKey cfg.lastHK, (*) => PasteHistory()

Delay(time) {
  g := Gui('-Caption +Border +ToolWindow +AlwaysOnTop')
  g.BackColor := Theme.Themes.Light().window_Bgc, g.SetFont('s30')
  text := g.AddText('xm', time)
  g.Show('NA y' A_ScreenHeight / 3)
  loop time
    Sleep(1000), text.Value := time - A_Index
  g.Destroy()
}

Start(Before := unset) {
  global isReady
  if !isReady
    return
  isReady := false
  IsSet(Before) && Before()
  Tip.ShowTip(''), StaticBG.Show(), Cursor.SetIcon(Cursor.Icon.cross), Tip.ShowTip('READY', , , 0)
  Hotkey 'LButton', cfg.pasteInstantly ? StartClip : StartClipWithToolBar, 'On'
  Hotkey cfg.cancelHK, Cancel, 'On'
  Hotkey 'RButton Up', Cancel, 'On'
}

#Esc:: {
  if MsgBox('Exit?', , 0x1) == 'OK'
    logger.Info('热键退出程序'), ExitApp()
}

ClearAll(*) {
  list := []
  for w in WinGetList('ahk_pid ' ProcessExist()) {
    if (g := GuiFromHwnd(w)) is PasteGui
      list.Push(g)
    if g is FullScreenGui
      return
  }
  if !list.Length
    return
  for v in list
    v.OnDestroy()
  History.Reset()
  Tip.ShowTip('Clear all')
}

StartClip(*) {
  logger.Info('立即截图，当前窗口：' WinGetTitle('A') || WinGetClass('A'))
  Tip.ShowTip(''), Mask.Show()
  g := GetGui(cfg.withTip)
  ResetState(), HotKeysOff(cfg.cancelHK, 'LButton')
  g.GetPos(, , &w, &h), g.RegisterEvent()
  Paste(g.Hwnd, (*) => AddFrameShadow(g)), AutoSave(g)
}

StartClipWithToolBar(*) {
  g := GetGui(cfg.withTip)
  Tip.ShowTip(''), Cursor.SetIcon(Cursor.Icon.arrow), Mask.Show()
  Hotkey 'LButton', 'Off'
  g.GetPos(&x, &y, &w, &h)
  bar := MyToolBar(2, ['取消', '确定'], x + w - MyToolBar.btnW * 2, y + h
    , [(*) => Cancel_(bar, g), (*) => Bar_OkCB(bar, g)])
  BarAdapt(&bx, &by, x, y, w, h, bar.btnCount), bar.Show(bx, by)
  Hotkey cfg.cancelHK, (*) => Cancel_(bar, g), 'On'
  Hotkey 'RButton Up', (*) => Cancel_(bar, g), 'On'
  Hotkey 'LButton', (*) => ChangeSize(bar, g), 'On'
  HotKey 'Left', (*) => HotKeyMoveWinAndToolBar(g, bar, 1), 'On'
  HotKey 'Right', (*) => HotKeyMoveWinAndToolBar(g, bar, -1), 'On'
  HotKey 'Up', (*) => HotKeyMoveWinAndToolBar(g, bar, 2), 'On'
  HotKey 'Down', (*) => HotKeyMoveWinAndToolBar(g, bar, -2), 'On'
  Hotkey '^Left', (*) => TinyAdapter(g, bar, 1), 'On'
  Hotkey '^Right', (*) => TinyAdapter(g, bar, -1), 'On'
  Hotkey '^Up', (*) => TinyAdapter(g, bar, 2), 'On'
  Hotkey '^Down', (*) => TinyAdapter(g, bar, -2), 'On'
  Hotkey '+Left', (*) => TinyAdapter(g, bar, 1, true), 'On'
  Hotkey '+Right', (*) => TinyAdapter(g, bar, -1, true), 'On'
  Hotkey '+Up', (*) => TinyAdapter(g, bar, 2, true), 'On'
  Hotkey '+Down', (*) => TinyAdapter(g, bar, -2, true), 'On'
}

AutoSave(g) {
  if !cfg.autoSave
    return
  logger.Info('自动保存截图')
  g.savePath := f := SaveToFileEx(g.Hwnd, cfg.hisPath)
  id := History.AddFile(f), HistoryGui.FlushCache()
  g.id := id
}

ResetState() {
  StaticBG.Close(), Mask.Close(), Cursor.SetIcon(Cursor.Icon.arrow), ToolTip()
  global isReady := true
}

Cancel(*) {
  HotKeysOff(cfg.cancelHK, 'RButton Up', 'LButton')
  ResetState(), Tip.ShowTip('CANCEL')
}

Cancel_(bar, g, *) {
  HotKeysOff(cfg.cancelHK, 'RButton Up', 'LButton', 'Left', 'Right', 'Up', 'Down'
    , '^Left', '^Right', '^Up', '^Down'
    , '+Left', '+Right', '+Up', '+Down')
  bar.Destroy(), g.Destroy(), ResetState(), Tip.ShowTip('CANCEL')
}

Bar_OkCB(bar, g) {
  logger.Info('微调截图，当前窗口：' WinGetTitle('A') || WinGetClass('A'))
  HotKeysOff(cfg.cancelHK, 'RButton Up', 'LButton', 'Left', 'Right', 'Up', 'Down'
    , '^Left', '^Right', '^Up', '^Down'
    , '+Left', '+Right', '+Up', '+Down')
  g.RegisterEvent(), Mask.Close(), bar.Destroy()
  Paste(g.Hwnd, , g.HasOpt('Border'))
  AddFrameShadow(g), StaticBG.Close(), ToolTip()
  global isReady := true
  AutoSave(g)
}

ChangeSize(bar, g, *) {
  if IsOverGui(bar) {
    while GetKeyState('LButton', 'p')
      Sleep 50
    if IsOverGui(bar)
      Send '{LButton}'
    return
  }
  if !IsOverGui(g)
    return
  direction := OverWhere(g)
  switch direction {
    case 0: t := Cursor.Icon.sizeAll
    case 2, 19: t := Cursor.Icon.sizeNS
    case 9, 22: t := Cursor.Icon.sizeNESW
    case 26, 5: t := Cursor.Icon.sizeNWSE
    case 3, 7: t := Cursor.Icon.sizeWE
  }
  MoveWinAndToolBar(g, bar, t, direction)

  IsOverGui(guiObj) {
    guiObj.GetPos(&guiX, &guiY, &guiW, &guiH), MouseGetPos(&x, &y)
    return x > guiX && x < guiX + guiW && y > guiY && y < guiY + guiH
  }

  OverWhere(guiObj) {
    guiObj.GetPos(&guiX, &guiY, &guiW, &guiH), MouseGetPos(&x, &y)
    bT := (guiW > 60 && guiH > 60) ? 25 : 10, r := 0
    if x < guiX + bT                            ; left
      r += 3
    else if x > guiX + guiW - bT                ; right
      r += 7
    if y < guiY + bT                            ; up
      r += 2
    else if y > guiY + guiH - bT                ; bottom
      r += 19
    return r
  }
}

TinyAdapter(frame, bar, dire, reverse := false) {
  frame.GetPos(&wx, &wy, &ww, &wh)
  if dire = 1 && wx > 0                           ; left
    reverse ? frame.Move(, , ww - 1) : frame.Move(wx - 1, , ww + 1)
  else if dire = -1 && wx + ww < A_ScreenWidth    ; right
    reverse ? frame.Move(wx + 1, , ww - 1) : frame.Move(, , ww + 1)
  else if dire = 2 && wy > 0                      ; up
    reverse ? frame.Move(, , , wh - 1) : frame.Move(, wy - 1, , wh + 1)
  else if dire = -2 && wy + wh < A_ScreenHeight   ; down
    reverse ? frame.Move(, wy + 1, , wh - 1) : frame.Move(, , , wh + 1)
  frame.GetPos(&wx, &wy, &ww, &wh)
  BarAdapt(&x, &y, wx, wy, ww, wh, bar.btnCount)
  bar.Move(x, y)
  ToolTip ww 'X' wh, wx, y
}

HotKeyMoveWinAndToolBar(frame, bar, dire) {
  frame.GetPos(&wx, &wy, &ww, &wh)
  if dire = 1 && wx > 0                           ; left
    frame.Move(wx - 1)
  else if dire = -1 && wx + ww < A_ScreenWidth    ; right
    frame.Move(wx + 1)
  else if dire = 2 && wy > 0                      ; up
    frame.Move(, wy - 1)
  else if dire = -2 && wy + wh < A_ScreenHeight   ; down
    frame.Move(, wy + 1)
  frame.GetPos(&wx, &wy, &ww, &wh)
  BarAdapt(&x, &y, wx, wy, ww, wh, bar.btnCount)
  bar.Move(x, y)
  if cfg.withTip
    ToolTip ww 'X' wh, wx, y
}

BarAdapt(&x, &y, wx, wy, ww, wh, btnCount) {
  y := wy + wh >= (A_ScreenHeight - MyToolBar.btnH) ? wy - MyToolBar.btnH : wy + wh
  y := wh >= (A_ScreenHeight - MyToolBar.btnH) ? wy + wh - 1.5 * MyToolBar.btnH : y
  x := ((wx + ww) < MyToolBar.btnW * btnCount) ? 0 : wx + ww - MyToolBar.btnW * btnCount
}

MoveWinAndToolBar(frame, bar, cursorIcon, dire, *) {
  bar.Hide(), frame.Show(), ToolTip(), MouseGetPos(&px, &py), frame.GetPos(&wx, &wy, &ww, &wh)
  dx := wx - px, dy := wy - py
  SetWinDelay(-1), Cursor.SetIcon(cursorIcon)
  While GetKeyState("LButton", "P") {
    MouseGetPos(&nx, &ny)
    if dire = 0 {
      x := nx + dx <= 0 ? 0 : nx + dx
      x := ww + dx + nx >= A_ScreenWidth ? A_ScreenWidth - ww : x
      y := ny + dy <= 0 ? 0 : ny + dy
      y := wh + dy + ny >= A_ScreenHeight ? A_ScreenHeight - wh : y
      frame.Move(x, y)
    } else if dire = 3                              ; left
      frame.Move(nx + dx, , ww + px - nx)
    else if dire = 7                                ; right
      frame.Move(, , ww + nx - px)
    else if dire = 2                                ; up
      frame.Move(, ny + dy, , wh + py - ny)
    else if dire = 19                               ; down
      frame.Move(, , , wh + ny - py)
    else if dire = 5                                ; ne
      frame.Move(nx + dx, ny + dy, ww + px - nx, wh + py - ny)
    else if dire = 26                               ; sw
      frame.Move(, , ww + nx - px, wh + ny - py)
    else if dire = 9                                ; nw
      frame.Move(, ny + dy, ww + nx - px, wh + py - ny)
    else if dire = 22                               ; se
      frame.Move(nx + dx, , ww + px - nx, wh + ny - py)
  }
  Cursor.SetIcon(Cursor.Icon.arrow), frame.GetPos(&wx, &wy, &ww, &wh)
  if wx < 0
    ww += wx, wx := 0
  if wy < 0
    wh += wy, wy := 0
  if wx + ww > A_ScreenWidth
    ww := A_ScreenWidth - wx
  if wy + wh > A_ScreenHeight
    wh := A_ScreenHeight - wy
  frame.Move(wx, wy, ww, wh), BarAdapt(&x, &y, wx, wy, ww, wh, bar.btnCount)
  bar.Move(x, y), bar.Show()
  if cfg.withTip
    ToolTip ww 'X' wh, wx, y
}

GetGui(withTip := true) {
  Hook._Exec(Events.BeforeClip)
  g := PasteGui()
  if cfg.useRandomBgc
    cfg.guiBgc := GetRandomColor()
  g.BackColor := cfg.guiBgc
  MouseGetPos &begin_x, &begin_y
  g.Show('x' begin_x ' y' begin_y)
  if cfg.noBgc
    WinSetTransColor(cfg.guiBgc, g)
  else WinSetTransparent(cfg.guiTransparent, g)
  last_x := 0, last_y := 0
  while GetKeyState('LButton', 'P') {
    MouseGetPos &end_x, &end_y
    x := End_x < Begin_x ? End_x : Begin_x, y := End_y < Begin_y ? End_y : Begin_y
    scope_x := Abs(begin_x - end_x), scope_y := Abs(begin_y - end_y)
    if withTip && (last_x != end_x || last_y != end_y)
      ToolTip(scope_x 'X' scope_y, x, y - 20, 1), ToolTip('(' end_x ',' end_y ')', end_x, end_y + 20, 2)
    last_x := end_x, last_y := end_y
    g.Move(x, y, scope_x, scope_y)
  }
  Hook._Exec(Events.AfterClip)
  ToolTip(), ToolTip(, , , 2)
  return g

  GetRandomColor() => ('0x' Floor(Random(0, 16777216)).toBase(16)).padEnd(8, '0')
}

Paste(hwnd, CallBack := unset, borderOpt := true, triggerHook := true) {
  if triggerHook
    Hook._Exec(Events.BeforePaste, GuiFromHwnd(hwnd))
  hdd_frame := DllCall("GetDC", 'ptr', 0), hdc_frame := DllCall("GetDC", 'ptr', hwnd)
  WinSetTransColor(cfg.guiBgc, 'ahk_id' hwnd), WinGetPos(&x, &y, &w, &h, 'ahk_id' hwnd)
  if borderOpt                                   ; remove border
    x += 1, y += 1
  DllCall("StretchBlt"
    , 'ptr', hdc_frame, 'int', 0, 'int', 0, 'int', w, 'int', h
    , 'ptr', hdd_frame, 'int', x, 'int', y, 'int', w, 'int', h
    , 'UInt', 0xCC0020)
  DllCall('ReleaseDC', 'int', 0, 'ptr', hdc_frame), DllCall('ReleaseDC', 'int', 0, 'ptr', hdd_frame)
  IsSet(CallBack) && CallBack()
  if triggerHook
    Hook._Exec(Events.AfterPaste)
}

PasteHistory(*) {
  if !History.FileList.Length {
    Tip.ShowTip('没有历史截图喵~', , , 1000)
    return
  }
  histItem := History.Consume()
  if histItem {
    ReadBMPWH(histItem.fullPath, &w, &h)
    g := HistoryGui(histItem.id, histItem.fullPath)
    g.Show(w, h, histItem.id)
    DisplayBitmap(histItem.fullPath, g.Hwnd, w, h)
  } else Tip.ShowTip('全部贴上了喵...', , , 1000)
}

PasteBMPFile(path) {
  if !path
    return
  ReadBMPWH(path, &w, &h)
  g := PasteGui('-Border'), x := 0, y := 0
  if w <= A_ScreenWidth - 100
    x := 100
  if h <= A_ScreenHeight - 100
    y := 100
  g.Show('x' x ' y' y ' w' w ' h' h), g.RegisterEvent()
  DisplayBitmap(path, g.Hwnd, w, h), WinSetTransparent(255, g)
}